@agent-native/core 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +17 -56
  2. package/dist/cli/recap.d.ts.map +1 -1
  3. package/dist/cli/recap.js +24 -13
  4. package/dist/cli/recap.js.map +1 -1
  5. package/dist/cli/skills.d.ts +2 -6
  6. package/dist/cli/skills.d.ts.map +1 -1
  7. package/dist/cli/skills.js +8 -66
  8. package/dist/cli/skills.js.map +1 -1
  9. package/dist/client/blocks/index.d.ts +11 -0
  10. package/dist/client/blocks/index.d.ts.map +1 -1
  11. package/dist/client/blocks/index.js +11 -0
  12. package/dist/client/blocks/index.js.map +1 -1
  13. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  14. package/dist/client/blocks/library/AnnotatedCodeBlock.js +2 -2
  15. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  16. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  17. package/dist/client/blocks/library/DiffBlock.js +86 -21
  18. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  19. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  20. package/dist/client/blocks/library/FileTreeBlock.js +27 -4
  21. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  22. package/dist/client/blocks/library/JsonExplorerBlock.js +1 -1
  23. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  24. package/dist/client/blocks/library/MermaidBlock.js +1 -1
  25. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  26. package/dist/client/blocks/library/annotation-rail.d.ts +19 -0
  27. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  28. package/dist/client/blocks/library/annotation-rail.js +19 -0
  29. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  30. package/dist/client/blocks/library/callout.config.d.ts +29 -0
  31. package/dist/client/blocks/library/callout.config.d.ts.map +1 -0
  32. package/dist/client/blocks/library/callout.config.js +33 -0
  33. package/dist/client/blocks/library/callout.config.js.map +1 -0
  34. package/dist/client/blocks/library/callout.d.ts +20 -0
  35. package/dist/client/blocks/library/callout.d.ts.map +1 -0
  36. package/dist/client/blocks/library/callout.js +61 -0
  37. package/dist/client/blocks/library/callout.js.map +1 -0
  38. package/dist/client/blocks/library/checklist.d.ts.map +1 -1
  39. package/dist/client/blocks/library/checklist.js +3 -3
  40. package/dist/client/blocks/library/checklist.js.map +1 -1
  41. package/dist/client/blocks/library/decision.config.d.ts +37 -0
  42. package/dist/client/blocks/library/decision.config.d.ts.map +1 -0
  43. package/dist/client/blocks/library/decision.config.js +32 -0
  44. package/dist/client/blocks/library/decision.config.js.map +1 -0
  45. package/dist/client/blocks/library/decision.d.ts +19 -0
  46. package/dist/client/blocks/library/decision.d.ts.map +1 -0
  47. package/dist/client/blocks/library/decision.js +119 -0
  48. package/dist/client/blocks/library/decision.js.map +1 -0
  49. package/dist/client/blocks/library/diagram.config.d.ts +64 -0
  50. package/dist/client/blocks/library/diagram.config.d.ts.map +1 -0
  51. package/dist/client/blocks/library/diagram.config.js +111 -0
  52. package/dist/client/blocks/library/diagram.config.js.map +1 -0
  53. package/dist/client/blocks/library/diagram.d.ts +16 -0
  54. package/dist/client/blocks/library/diagram.d.ts.map +1 -0
  55. package/dist/client/blocks/library/diagram.js +261 -0
  56. package/dist/client/blocks/library/diagram.js.map +1 -0
  57. package/dist/client/blocks/library/question-form.config.d.ts +69 -0
  58. package/dist/client/blocks/library/question-form.config.d.ts.map +1 -0
  59. package/dist/client/blocks/library/question-form.config.js +58 -0
  60. package/dist/client/blocks/library/question-form.config.js.map +1 -0
  61. package/dist/client/blocks/library/question-form.d.ts +20 -0
  62. package/dist/client/blocks/library/question-form.d.ts.map +1 -0
  63. package/dist/client/blocks/library/question-form.js +286 -0
  64. package/dist/client/blocks/library/question-form.js.map +1 -0
  65. package/dist/client/blocks/library/sanitize-html.d.ts +5 -0
  66. package/dist/client/blocks/library/sanitize-html.d.ts.map +1 -0
  67. package/dist/client/blocks/library/sanitize-html.js +240 -0
  68. package/dist/client/blocks/library/sanitize-html.js.map +1 -0
  69. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  70. package/dist/client/blocks/library/server-specs.js +59 -0
  71. package/dist/client/blocks/library/server-specs.js.map +1 -1
  72. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  73. package/dist/client/blocks/library/specs.js +11 -0
  74. package/dist/client/blocks/library/specs.js.map +1 -1
  75. package/dist/client/blocks/library/tabs.d.ts.map +1 -1
  76. package/dist/client/blocks/library/tabs.js +12 -12
  77. package/dist/client/blocks/library/tabs.js.map +1 -1
  78. package/dist/client/blocks/library/wireframe-kit.d.ts +260 -0
  79. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -0
  80. package/dist/client/blocks/library/wireframe-kit.js +920 -0
  81. package/dist/client/blocks/library/wireframe-kit.js.map +1 -0
  82. package/dist/client/blocks/library/wireframe.config.d.ts +123 -0
  83. package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -0
  84. package/dist/client/blocks/library/wireframe.config.js +294 -0
  85. package/dist/client/blocks/library/wireframe.config.js.map +1 -0
  86. package/dist/client/blocks/library/wireframe.d.ts +15 -0
  87. package/dist/client/blocks/library/wireframe.d.ts.map +1 -0
  88. package/dist/client/blocks/library/wireframe.js +206 -0
  89. package/dist/client/blocks/library/wireframe.js.map +1 -0
  90. package/dist/client/blocks/registry.d.ts +9 -0
  91. package/dist/client/blocks/registry.d.ts.map +1 -1
  92. package/dist/client/blocks/registry.js +12 -5
  93. package/dist/client/blocks/registry.js.map +1 -1
  94. package/dist/client/blocks/server.d.ts +1 -0
  95. package/dist/client/blocks/server.d.ts.map +1 -1
  96. package/dist/client/blocks/server.js +1 -0
  97. package/dist/client/blocks/server.js.map +1 -1
  98. package/dist/client/blocks/types.d.ts +8 -0
  99. package/dist/client/blocks/types.d.ts.map +1 -1
  100. package/dist/client/blocks/types.js.map +1 -1
  101. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  102. package/dist/client/rich-markdown-editor/DragHandle.js +77 -12
  103. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  104. package/dist/styles/agent-native.css +1 -0
  105. package/dist/styles/blocks.css +1380 -0
  106. package/docs/content/plan-plugin.md +8 -8
  107. package/docs/content/pr-visual-recap.md +2 -2
  108. package/docs/content/template-plan.md +94 -17
  109. package/package.json +2 -1
  110. package/docs/content/visual-plans.md +0 -82
package/README.md CHANGED
@@ -19,6 +19,21 @@ The agent and the UI are equal citizens of the same system. Every action works b
19
19
  - **Any database, any host** — Any SQL database Drizzle supports. Any hosting target Nitro supports. No lock-in.
20
20
  - **Any AI agent** — Claude Code, Codex, Gemini CLI, OpenCode, or Builder.io. Use whichever agent you prefer.
21
21
 
22
+ ## Try it with a skill
23
+
24
+ Don't want to scaffold a whole app yet? Add agent-native superpowers to a coding agent you already use — Claude Code, Codex, or Cursor — with one command:
25
+
26
+ ```bash
27
+ npx @agent-native/core@latest skills add visual-plan
28
+ ```
29
+
30
+ It installs the skills, registers the hosted MCP connector, and signs you in in one step. You get two slash commands that upgrade how your agent plans and reports its work:
31
+
32
+ - **`/visual-plan`** — before the agent writes code, it opens a structured, reviewable plan document instead of a wall of text: inline diagrams, UI wireframes and prototypes, file-by-file implementation maps, and annotations you can comment on and approve.
33
+ - **`/visual-recap`** — after changes land, it turns a PR or git diff into a high-altitude visual recap: schema, API, and file changes rendered as grounded before/after blocks with a shareable review link, instead of scrolling a raw diff.
34
+
35
+ See the **[Skills Guide](https://agent-native.com/docs/skills-guide#app-backed-skills)** for more skills and local installs.
36
+
22
37
  ## Templates
23
38
 
24
39
  Start from a complete, production-grade SaaS app — cloneable, not scaffolded. Each one replaces tools you're paying for, except you own everything and can customize it however you want. Not demos; products.
@@ -156,7 +171,7 @@ Generate forms from a prompt, branch logic with the agent, and own every respons
156
171
 
157
172
  **Brain**
158
173
 
159
- <a href="https://agent-native.com/templates/brain">Brain template</a>
174
+ <a href="https://agent-native.com/templates/brain"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9c9fe3b5b9494e33803cd3f494cba356?format=webp&width=800" alt="Brain template" width="100%" /></a>
160
175
 
161
176
  **Agent-Native company memory**
162
177
 
@@ -169,7 +184,7 @@ Ask questions over cited company knowledge from approved Slack, meetings, transc
169
184
 
170
185
  **Assets**
171
186
 
172
- <a href="https://agent-native.com/templates/assets">Assets template</a>
187
+ <a href="https://agent-native.com/templates/assets"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F769092170a14474f998cbca47384f891?format=webp&width=800" alt="Assets template" width="100%" /></a>
173
188
 
174
189
  **Agent-Native asset library**
175
190
 
@@ -198,60 +213,6 @@ Want a single app, no monorepo? Use `--standalone`:
198
213
  npx @agent-native/core create my-app --standalone --template mail
199
214
  ```
200
215
 
201
- ### Try it with a skill
202
-
203
- Don't want to scaffold a whole app yet? Add agent-native superpowers to a coding agent you already use — Claude Code, Codex, or Cursor — with one command. Installing the **Plans** skill turns the plans your agent writes into structured, reviewable docs with diagrams, wireframes, and inline comments:
204
-
205
- ```bash
206
- npx @agent-native/core@latest skills add visual-plan
207
- ```
208
-
209
- It installs the skill, registers the hosted MCP connector, and signs you in in one step — then run `/visual-plan`. See the **[Skills Guide](https://agent-native.com/docs/skills-guide#app-backed-skills)** for more skills and local installs.
210
-
211
- Need a coding agent workspace? `agent-native` or `agent-native code` opens an open-source Claude Code/Codex-like Code workspace with no prompt required. From there, type a task, run slash goals interactively, or call them directly from your shell:
212
-
213
- ```bash
214
- npx @agent-native/core@latest
215
- npx @agent-native/core@latest "fix the failing auth tests"
216
- npx @agent-native/core@latest code
217
- npx @agent-native/core@latest code "fix the failing auth tests"
218
- npx @agent-native/core@latest code exec "fix the failing auth tests"
219
- npx @agent-native/core@latest code -p "fix the failing auth tests"
220
- npx @agent-native/core@latest code --plan "explain the auth test failures"
221
- npx @agent-native/core@latest code --auto "fix the failing auth tests"
222
- npx @agent-native/core@latest code /migrate ./my-next-app --out ../migrated-app
223
- npx @agent-native/core@latest code /migrate ./my-next-app --emit ../migration-dossier
224
- npx @agent-native/core@latest code list
225
- npx @agent-native/core@latest code status --last
226
- npx @agent-native/core@latest code attach --last
227
- npx @agent-native/core@latest code logs --last
228
- npx @agent-native/core@latest code stop --last
229
- npx @agent-native/core@latest code ui
230
- npx @agent-native/core@latest code approve --last
231
- npx @agent-native/core@latest code resume --last
232
- npx @agent-native/core@latest code --continue "check the auth edge cases next"
233
- npx @agent-native/core@latest code resume --last "check the auth edge cases next"
234
- ```
235
-
236
- Slash goals can run from the interactive shell or directly from the command line, and `agent-native code goals` shows the goals registered in your checkout. A bare prompt starts a local coding-agent session, streams work, records transcript/status/tool events, and accepts follow-up prompts; `/migrate` is one specialized capability inside that general Code workspace. Project-specific slash commands live in `.agents/commands/*.md`, so teams can add prompts such as `/release-check` or `/migrate-commerce` without changing the framework. Installed `agent-native` with no arguments launches the Code workspace; `agent-native "fix tests"` starts an Agent-Native Code task directly. Use `agent-native create` when you want to create apps or workspaces.
237
-
238
- Working inside this repository? Use `pnpm dev` for the lazy framework dev gateway. It exposes the core templates on one local origin and starts each app only when you visit it, which keeps memory usage much lower than booting every server at once. To focus on specific templates, pass `--apps`, for example `pnpm dev -- --apps analytics,brain`. Use `pnpm dev:eager` only when you intentionally want the older eager mode that starts the selected template servers, frame, docs, and core watcher immediately. The old `pnpm dev:all` name still works as an alias for eager mode.
239
-
240
- Use `pnpm dev:cli ...` to run the source CLI without building first, for example `pnpm dev:cli --help` or `pnpm dev:cli code goals`.
241
-
242
- The Code workspace uses the familiar Codex/Claude-style session loop: pick a previous session, list runs, check status, attach to live output, print logs, stop work, resume with context, open the local UI, and continue the same run from Desktop or CLI. The Desktop Code tab, `agent-native code ui`, background sessions, and sub-agent sessions all converge on the shared run harness/controller model instead of separate ad hoc runners. The primary modes are intentionally simple:
243
-
244
- - **Plan mode** (`--plan`) inspects, explains, and proposes without writing files.
245
- - **Auto mode** (`--auto`, default) edits files, runs checks, and only pauses for genuinely destructive file, git, publish, or data operations.
246
-
247
- `agent-native migrate` still works as a direct shortcut; `code /migrate` is the Agent-Native Code entrypoint for the migration goal. By default it creates an Agent-Native Code session and portable migration dossier, not a scaffolded app/template. `resume --last` reopens the latest run handoff; adding a quoted prompt records it as a follow-up transcript event for that run so the next active coding agent can pick it up. If a high-risk command is paused for approval, `code approve --last` runs that one pending command and then points you back to resume the session. Use `--app-surface` only when you want the legacy hidden migration detail app for assessment, approval, tasks, artifacts, and verification.
248
- Use `--emit` when you want only the portable dossier for Codex, Claude Code, Cursor, or another coding agent.
249
- Agent-Native Code also includes lightweight goals such as `/audit`:
250
-
251
- ```bash
252
- npx @agent-native/core@latest code /audit --url https://example.com
253
- ```
254
-
255
216
  ## Workspaces (Monorepo)
256
217
 
257
218
  A workspace is the default shape of an agent-native project. Every app sits under `apps/`, and `packages/shared/` is available for the small amount of code, instructions, skills, or branding that should truly apply to every app.
@@ -1 +1 @@
1
- {"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../../src/cli/recap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAkDH,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAQzC,CAAC;AAEF,+DAA+D;AAC/D,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB,CAOA;AA6BD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAa5D;AAMD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAsB,GAAG;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAgBA;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GAAG,MAAM,CAkDT;AA8ID,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAgG7E;AAmPD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B5D"}
1
+ {"version":3,"file":"recap.d.ts","sourceRoot":"","sources":["../../src/cli/recap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAkDH,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAQzC,CAAC;AAEF,+DAA+D;AAC/D,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB,CAOA;AA6BD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAa5D;AAMD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAsB,GAAG;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAgBA;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GAAG,MAAM,CAkDT;AAgJD,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAwF7E;AAiQD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B5D"}
package/dist/cli/recap.js CHANGED
@@ -162,7 +162,7 @@ export function buildRecapPrompt(input) {
162
162
  ? `, passing \`planId: "${input.prevPlanId}"\` so this REPLACES the existing recap plan`
163
163
  : ""}.`);
164
164
  lines.push(`2. Call the **set-resource-visibility** tool on the \`plan\` MCP server with \`{ resourceType: "plan", resourceId: <the returned plan id>, visibility: "org" }\` so the recap is login-gated to the org, never public.`);
165
- lines.push(`3. Write the plan URL to a file named \`recap-url.txt\` at the repo root, containing exactly one line: \`${appUrl}/plans/<the returned plan id>\`. This file is the workflow's only hand-off — do not print anything else as the deliverable.`);
165
+ lines.push(`3. Write the plan URL to a file named \`recap-url.txt\` at the repo root, containing exactly one line: \`${appUrl}/recaps/<the returned plan id>\`. This file is the workflow's only hand-off — do not print anything else as the deliverable.`);
166
166
  lines.push("");
167
167
  lines.push("Do not invent file names, schema fields, or endpoints. Redact anything that looks like a secret. If the diff has no reviewable substance, still publish a minimal recap and write recap-url.txt.");
168
168
  lines.push("");
@@ -240,7 +240,9 @@ async function upsertComment(input) {
240
240
  return { action: "created", id: created.id, html_url: created.html_url };
241
241
  }
242
242
  function planIdFromUrl(url) {
243
- const match = url.match(/\/plans\/([A-Za-z0-9_-]+)/);
243
+ // Accept both /recaps/<id> (the canonical recap route the agent now writes)
244
+ // and /plans/<id> (legacy URLs) so the sticky-comment rebuild keeps working.
245
+ const match = url.match(/\/(?:recaps|plans)\/([A-Za-z0-9_-]+)/);
244
246
  return match ? match[1] : null;
245
247
  }
246
248
  /** True when both URLs parse and share an origin. */
@@ -264,7 +266,6 @@ function originOf(url) {
264
266
  /** Build the sticky comment body from the workflow's environment. */
265
267
  export function buildCommentBody(env = process.env) {
266
268
  const headShort = (env.HEAD_SHA || "").slice(0, 7);
267
- const aid = "_A visual recap is an aid, not a replacement for reviewing the diff._";
268
269
  const lines = [MARKER];
269
270
  if (env.SUPPRESSED === "true") {
270
271
  let reason = "potential secret in diff";
@@ -281,8 +282,6 @@ export function buildCommentBody(env = process.env) {
281
282
  lines.push("The recap was **suppressed** because the diff matched a secret/credential pattern. No plan was published.");
282
283
  lines.push("");
283
284
  lines.push(`Reason: \`${reason}\`. Updated for \`${headShort}\`.`);
284
- lines.push("");
285
- lines.push(aid);
286
285
  return lines.join("\n");
287
286
  }
288
287
  // Tiny diffs aren't worth a recap. Refresh an existing sticky comment to this
@@ -294,8 +293,6 @@ export function buildCommentBody(env = process.env) {
294
293
  lines.push("The change in this push is too small to be worth a visual recap. This is informational only and does **not** block the PR.");
295
294
  lines.push("");
296
295
  lines.push(`Updated for \`${headShort}\`.`);
297
- lines.push("");
298
- lines.push(aid);
299
296
  return lines.join("\n");
300
297
  }
301
298
  const planUrl = (env.PLAN_URL || "").trim();
@@ -309,15 +306,13 @@ export function buildCommentBody(env = process.env) {
309
306
  const planId = planUrl ? planIdFromUrl(planUrl) : null;
310
307
  const sameOriginOk = appUrl === "" || sameOrigin(planUrl, appUrl);
311
308
  const base = (appUrl || originOf(planUrl)).replace(/\/$/, "");
312
- const safeUrl = planId && base && sameOriginOk ? `${base}/plans/${planId}` : "";
309
+ const safeUrl = planId && base && sameOriginOk ? `${base}/recaps/${planId}` : "";
313
310
  if (!safeUrl) {
314
311
  lines.push("### Visual recap — generation failed");
315
312
  lines.push("");
316
313
  lines.push("The visual recap could not be generated for this push. This is informational only and does **not** block the PR.");
317
314
  lines.push("");
318
315
  lines.push(`Updated for \`${headShort}\`.`);
319
- lines.push("");
320
- lines.push(aid);
321
316
  return lines.join("\n");
322
317
  }
323
318
  // The image URL is produced by our own recap-image route, but validate it is
@@ -341,7 +336,7 @@ export function buildCommentBody(env = process.env) {
341
336
  lines.push("> Large diff — this recap is a **summarized** view (top files + schema/API deltas).");
342
337
  }
343
338
  lines.push("");
344
- lines.push(`Updated for \`${headShort}\`. ${aid}`);
339
+ lines.push(`Updated for \`${headShort}\`.`);
345
340
  lines.push("");
346
341
  lines.push(`<!-- plan-id: ${planId} -->`);
347
342
  return lines.join("\n");
@@ -461,7 +456,7 @@ async function runShot(args) {
461
456
  try {
462
457
  browser = await chromium.launch({ args: ["--no-sandbox"] });
463
458
  const context = await browser.newContext({
464
- viewport: { width: 1280, height: 900 },
459
+ viewport: { width: 1450, height: 1450 },
465
460
  deviceScaleFactor: 2,
466
461
  });
467
462
  if (attachToken) {
@@ -503,7 +498,23 @@ async function runShot(args) {
503
498
  }
504
499
  }
505
500
  await page.waitForTimeout(matched ? 1_200 : 500);
506
- await page.screenshot({ path: out, fullPage: true });
501
+ // Zoom out slightly so more content fits, then scroll past the main title.
502
+ await page.evaluate(() => {
503
+ document.documentElement.style.zoom = "80%";
504
+ });
505
+ await page.evaluate(() => {
506
+ const firstH2 = document.querySelector("h2");
507
+ if (firstH2) {
508
+ firstH2.scrollIntoView({ block: "start" });
509
+ window.scrollBy(0, -24);
510
+ }
511
+ else {
512
+ const h1 = document.querySelector("h1");
513
+ if (h1)
514
+ window.scrollBy(0, h1.getBoundingClientRect().bottom + 32);
515
+ }
516
+ });
517
+ await page.screenshot({ path: out });
507
518
  captured = true;
508
519
  await browser.close();
509
520
  }
@@ -1 +1 @@
1
- {"version":3,"file":"recap.js","sourceRoot":"","sources":["../../src/cli/recap.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `agent-native recap <scan|build-prompt|shot|comment>` — the helper surface\n * used by the PR Visual Recap GitHub Action.\n *\n * The action no longer generates the recap deterministically. Instead a coding\n * agent (Claude Code or Codex) RUNS THE REPO'S visual-recap skill against the\n * diff and publishes the plan via the plan MCP tools. These subcommands are the\n * thin, deterministic glue around that:\n *\n * scan Refuse to hand a secret-leaking diff to the agent.\n * build-prompt Assemble the agent prompt = repo SKILL.md + a task wrapper.\n * shot Screenshot the published plan and upload it to the plan app's\n * signed public image route (for an inline PR-comment image).\n * comment Find the previous plan id / upsert the sticky PR comment.\n *\n * Promoting these to the published CLI means an installed repo's workflow calls\n * `agent-native recap …` instead of copying helper scripts into the repo.\n *\n * Node built-ins only (plus an optional dynamic `playwright` import for `shot`).\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { PR_VISUAL_RECAP_WORKFLOW_YML } from \"./pr-visual-recap-workflow.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Arg parsing */\n/* -------------------------------------------------------------------------- */\n\nfunction parseArgs(argv: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i += 1) {\n const token = argv[i];\n if (!token.startsWith(\"--\")) continue;\n const key = token.slice(2);\n const next = argv[i + 1];\n if (next === undefined || next.startsWith(\"--\")) out[key] = true;\n else {\n out[key] = next;\n i += 1;\n }\n }\n return out;\n}\n\nfunction stringArg(\n args: Record<string, string | boolean>,\n key: string,\n): string {\n const value = args[key];\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(`Missing --${key}`);\n }\n return value;\n}\n\nfunction optionalArg(\n args: Record<string, string | boolean>,\n key: string,\n): string | undefined {\n const value = args[key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/* -------------------------------------------------------------------------- */\n/* GitHub Action install (used by `skills add … --with-github-action`) */\n/* -------------------------------------------------------------------------- */\n\n/** GitHub secrets the installed PR Visual Recap workflow needs. */\nexport const PR_VISUAL_RECAP_SETUP: string[] = [\n \"Required secrets:\",\n \" PLAN_RECAP_TOKEN — bearer token from `agent-native connect`\",\n \" ANTHROPIC_API_KEY — the LLM key for the default Claude Code backend\",\n \"Optional (only if you change defaults):\",\n \" OPENAI_API_KEY (secret) + VISUAL_RECAP_AGENT=codex (variable) — use Codex instead of Claude\",\n \" VISUAL_RECAP_MODEL / VISUAL_RECAP_REASONING (variables) — pin the model (e.g. gpt-5.5) and reasoning depth (none|minimal|low|medium|high|xhigh; Codex only)\",\n \" PLAN_RECAP_APP_URL (secret) — only when self-hosting the plan app (defaults to https://plan.agent-native.com)\",\n];\n\n/** Write .github/workflows/pr-visual-recap.yml into a repo. */\nexport function writePrVisualRecapWorkflow(baseDir: string): {\n path: string;\n existed: boolean;\n} {\n const dir = path.resolve(baseDir, \".github\", \"workflows\");\n fs.mkdirSync(dir, { recursive: true });\n const file = path.join(dir, \"pr-visual-recap.yml\");\n const existed = fs.existsSync(file);\n fs.writeFileSync(file, PR_VISUAL_RECAP_WORKFLOW_YML);\n return { path: path.relative(baseDir, file), existed };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Secret scan — defense-in-depth before any LLM sees the diff */\n/* -------------------------------------------------------------------------- */\n\n/**\n * If the diff contains anything that looks like a real secret, we refuse to\n * build a recap at all (rather than risk echoing it into a published plan).\n * These patterns intentionally err toward caution and scan added, removed, and\n * context lines so deleting a real secret does not leak it in a split diff.\n */\nconst SECRET_PATTERNS: RegExp[] = [\n // Common provider key prefixes.\n /\\b(?:sk|pk|rk)-[A-Za-z0-9]{16,}\\b/,\n /\\bghp_[A-Za-z0-9]{20,}\\b/,\n /\\bgithub_pat_[A-Za-z0-9_]{20,}\\b/,\n /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAKIA[0-9A-Z]{16}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n // Bearer / Authorization header values with an actual token.\n /authorization\\s*[:=]\\s*['\"]?bearer\\s+[A-Za-z0-9._-]{12,}/i,\n // Private key blocks.\n /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/,\n // `KEY=...`, `TOKEN=...`, `SECRET=...`, `PASSWORD=...` assigned a real-looking\n // value (long, non-placeholder).\n /\\b[A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|API_KEY|PRIVATE_KEY|ACCESS_KEY)[A-Z0-9_]*\\s*[:=]\\s*['\"]?(?!.*(?:your|example|placeholder|changeme|xxxx|\\*\\*\\*|<|\\$\\{|process\\.env|env\\.|REDACTED))[A-Za-z0-9/_+=.-]{16,}/i,\n];\n\nexport function lineLooksSecret(line: string): boolean {\n return SECRET_PATTERNS.some((re) => re.test(line));\n}\n\nexport function diffContainsSecret(diffText: string): boolean {\n for (const line of diffText.split(\"\\n\")) {\n if (\n line.startsWith(\"+\") ||\n line.startsWith(\"-\") ||\n line.startsWith(\" \") ||\n line.startsWith(\"+++\") ||\n line.startsWith(\"---\")\n ) {\n if (lineLooksSecret(line)) return true;\n }\n }\n return false;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Prompt builder — repo SKILL.md + task wrapper */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Locate the repo's visual-recap SKILL.md, preferring the host-agent install\n * locations so a user's `agent-native skills add` copy wins, then falling back\n * to the framework's own source locations.\n */\nexport function readRepoSkillMd(cwd: string = process.cwd()): {\n text: string;\n source: string;\n} {\n const candidates = [\n \".claude/skills/visual-recap/SKILL.md\",\n \".agents/skills/visual-recap/SKILL.md\",\n \"skills/visual-recap/SKILL.md\",\n \"templates/plan/.agents/skills/visual-recap/SKILL.md\",\n ];\n for (const rel of candidates) {\n const abs = path.resolve(cwd, rel);\n if (fs.existsSync(abs)) {\n return { text: fs.readFileSync(abs, \"utf8\"), source: rel };\n }\n }\n throw new Error(\n \"Could not find visual-recap/SKILL.md. Run `agent-native skills add visual-plan` first.\",\n );\n}\n\nexport function buildRecapPrompt(input: {\n skillMd: string;\n pr: string;\n head?: string;\n appUrl: string;\n diffPath: string;\n statPath?: string;\n prevPlanId?: string;\n huge?: boolean;\n}): string {\n const appUrl = input.appUrl.replace(/\\/$/, \"\");\n const lines: string[] = [];\n lines.push(\"# Task: publish a Visual Recap of this pull request\");\n lines.push(\"\");\n lines.push(\n `You are running non-interactively in CI. Follow the **visual-recap skill** included verbatim below to turn this PR's diff into a grounded Agent-Native Plan, then publish it.`,\n );\n lines.push(\"\");\n lines.push(\"## Inputs (read them from disk with your Read tool)\");\n lines.push(`- PR number: **#${input.pr}**`);\n if (input.head) lines.push(`- Head commit: \\`${input.head}\\``);\n lines.push(`- Unified diff: \\`${input.diffPath}\\` (read this file)`);\n if (input.statPath)\n lines.push(`- Diff stat: \\`${input.statPath}\\` (read this file)`);\n if (input.huge) {\n lines.push(\n `- The diff is LARGE — produce a **summarized** recap (top files + schema/API deltas), not an exhaustive one.`,\n );\n }\n lines.push(\"\");\n lines.push(\"## Publish (this is the only way to produce output)\");\n lines.push(\n `The \\`plan\\` MCP server is configured for you. Call its tools by name (your host may expose them as \\`create-visual-recap\\` or \\`mcp__plan__create-visual-recap\\` — same tool).`,\n );\n lines.push(\n `1. Call the **create-visual-recap** tool on the \\`plan\\` MCP server with grounded MDX derived ONLY from the real diff${\n input.prevPlanId\n ? `, passing \\`planId: \"${input.prevPlanId}\"\\` so this REPLACES the existing recap plan`\n : \"\"\n }.`,\n );\n lines.push(\n `2. Call the **set-resource-visibility** tool on the \\`plan\\` MCP server with \\`{ resourceType: \"plan\", resourceId: <the returned plan id>, visibility: \"org\" }\\` so the recap is login-gated to the org, never public.`,\n );\n lines.push(\n `3. Write the plan URL to a file named \\`recap-url.txt\\` at the repo root, containing exactly one line: \\`${appUrl}/plans/<the returned plan id>\\`. This file is the workflow's only hand-off — do not print anything else as the deliverable.`,\n );\n lines.push(\"\");\n lines.push(\n \"Do not invent file names, schema fields, or endpoints. Redact anything that looks like a secret. If the diff has no reviewable substance, still publish a minimal recap and write recap-url.txt.\",\n );\n lines.push(\"\");\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(\"# visual-recap skill (follow this exactly)\");\n lines.push(\"\");\n lines.push(input.skillMd.trim());\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* GitHub comment helpers */\n/* -------------------------------------------------------------------------- */\n\nconst MARKER = \"<!-- pr-visual-recap -->\";\n\ntype GitHubComment = {\n id: number;\n body?: string | null;\n html_url?: string;\n user?: { type?: string | null } | null;\n};\n\nfunction repoParts(repoFullName: string): { owner: string; repo: string } {\n const [owner, repo] = repoFullName.split(\"/\");\n if (!owner || !repo) throw new Error(`Invalid --repo: ${repoFullName}`);\n return { owner, repo };\n}\n\nasync function githubRequest<T>(\n token: string,\n apiPath: string,\n init: RequestInit = {},\n): Promise<T> {\n const res = await fetch(`https://api.github.com${apiPath}`, {\n ...init,\n headers: {\n accept: \"application/vnd.github+json\",\n authorization: `Bearer ${token}`,\n \"x-github-api-version\": \"2022-11-28\",\n ...(init.headers ?? {}),\n },\n });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(\n `GitHub request failed ${res.status} ${res.statusText}: ${detail.slice(0, 500)}`,\n );\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\nasync function findExistingComment(input: {\n token: string;\n owner: string;\n repo: string;\n issue: string;\n}): Promise<GitHubComment | null> {\n for (let page = 1; ; page += 1) {\n const comments = await githubRequest<GitHubComment[]>(\n input.token,\n `/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(\n input.repo,\n )}/issues/${encodeURIComponent(input.issue)}/comments?per_page=100&page=${page}`,\n );\n const match = comments.find(\n (comment) =>\n comment.user?.type === \"Bot\" &&\n typeof comment.body === \"string\" &&\n comment.body.includes(MARKER),\n );\n if (match) return match;\n if (comments.length < 100) return null;\n }\n}\n\nasync function upsertComment(input: {\n token: string;\n owner: string;\n repo: string;\n issue: string;\n body: string;\n /** When true, refresh an existing comment but never create a new one. */\n updateOnly?: boolean;\n}): Promise<{\n action: \"created\" | \"updated\" | \"skipped\";\n id: number;\n html_url?: string;\n}> {\n const body = input.body.includes(MARKER)\n ? input.body\n : `${MARKER}\\n${input.body}`;\n const existing = await findExistingComment(input);\n if (!existing && input.updateOnly) {\n // Nothing to refresh and we were told not to create — e.g. a tiny diff with\n // no prior recap. Stay silent rather than posting a \"skipped\" comment.\n return { action: \"skipped\", id: 0 };\n }\n if (existing) {\n const updated = await githubRequest<GitHubComment>(\n input.token,\n `/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(\n input.repo,\n )}/issues/comments/${existing.id}`,\n {\n method: \"PATCH\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ body }),\n },\n );\n return { action: \"updated\", id: existing.id, html_url: updated.html_url };\n }\n const created = await githubRequest<GitHubComment>(\n input.token,\n `/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(\n input.repo,\n )}/issues/${encodeURIComponent(input.issue)}/comments`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ body }),\n },\n );\n return { action: \"created\", id: created.id, html_url: created.html_url };\n}\n\nfunction planIdFromUrl(url: string): string | null {\n const match = url.match(/\\/plans\\/([A-Za-z0-9_-]+)/);\n return match ? match[1] : null;\n}\n\n/** True when both URLs parse and share an origin. */\nfunction sameOrigin(a: string, b: string): boolean {\n try {\n return new URL(a).origin === new URL(b).origin;\n } catch {\n return false;\n }\n}\n\n/** The origin of a URL, or \"\" if it doesn't parse. */\nfunction originOf(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return \"\";\n }\n}\n\n/** Build the sticky comment body from the workflow's environment. */\nexport function buildCommentBody(env: NodeJS.ProcessEnv = process.env): string {\n const headShort = (env.HEAD_SHA || \"\").slice(0, 7);\n const aid =\n \"_A visual recap is an aid, not a replacement for reviewing the diff._\";\n const lines: string[] = [MARKER];\n\n if (env.SUPPRESSED === \"true\") {\n let reason = \"potential secret in diff\";\n try {\n const parsed = JSON.parse(env.SUPPRESSED_JSON || \"{}\");\n if (parsed && typeof parsed.reason === \"string\") reason = parsed.reason;\n } catch {\n /* keep default */\n }\n lines.push(\"### Visual recap — not generated\");\n lines.push(\"\");\n lines.push(\n \"The recap was **suppressed** because the diff matched a secret/credential pattern. No plan was published.\",\n );\n lines.push(\"\");\n lines.push(`Reason: \\`${reason}\\`. Updated for \\`${headShort}\\`.`);\n lines.push(\"\");\n lines.push(aid);\n return lines.join(\"\\n\");\n }\n\n // Tiny diffs aren't worth a recap. Refresh an existing sticky comment to this\n // state (the workflow only updates, never creates, on tiny) so it never lingers\n // pointing at a stale head SHA.\n if (env.DIFF_TINY === \"true\") {\n lines.push(\"### Visual recap — skipped (diff too small)\");\n lines.push(\"\");\n lines.push(\n \"The change in this push is too small to be worth a visual recap. This is informational only and does **not** block the PR.\",\n );\n lines.push(\"\");\n lines.push(`Updated for \\`${headShort}\\`.`);\n lines.push(\"\");\n lines.push(aid);\n return lines.join(\"\\n\");\n }\n\n const planUrl = (env.PLAN_URL || \"\").trim();\n const appUrl = (env.PLAN_RECAP_APP_URL || \"\").trim();\n // recap-url.txt is agent-written → untrusted. Rebuild a canonical link from a\n // TRUSTED base (the configured PLAN_RECAP_APP_URL when set, else the parsed\n // origin of the plan URL) plus a strictly-validated plan id, instead of\n // embedding the raw URL. That both enforces the app origin and prevents\n // markdown injection — a same-origin URL with a crafted path/query could\n // otherwise break out of the markdown link.\n const planId = planUrl ? planIdFromUrl(planUrl) : null;\n const sameOriginOk = appUrl === \"\" || sameOrigin(planUrl, appUrl);\n const base = (appUrl || originOf(planUrl)).replace(/\\/$/, \"\");\n const safeUrl =\n planId && base && sameOriginOk ? `${base}/plans/${planId}` : \"\";\n if (!safeUrl) {\n lines.push(\"### Visual recap — generation failed\");\n lines.push(\"\");\n lines.push(\n \"The visual recap could not be generated for this push. This is informational only and does **not** block the PR.\",\n );\n lines.push(\"\");\n lines.push(`Updated for \\`${headShort}\\`.`);\n lines.push(\"\");\n lines.push(aid);\n return lines.join(\"\\n\");\n }\n\n // The image URL is produced by our own recap-image route, but validate it is\n // same-origin and matches the canonical hex-token path before embedding it, so\n // it likewise cannot inject markdown.\n const imageUrlRaw = (env.RECAP_IMAGE_URL || \"\").trim();\n const imageUrl =\n imageUrlRaw &&\n sameOrigin(imageUrlRaw, base) &&\n /\\/_agent-native\\/recap-image\\/[0-9a-f]+\\.png$/.test(imageUrlRaw)\n ? imageUrlRaw\n : \"\";\n lines.push(\"### Visual recap — review at a higher altitude\");\n lines.push(\"\");\n if (imageUrl) {\n lines.push(`[![Visual recap](${imageUrl})](${safeUrl})`);\n lines.push(\"\");\n }\n lines.push(`**[Open the interactive recap](${safeUrl})**`);\n if (env.DIFF_HUGE === \"true\") {\n lines.push(\"\");\n lines.push(\n \"> Large diff — this recap is a **summarized** view (top files + schema/API deltas).\",\n );\n }\n lines.push(\"\");\n lines.push(`Updated for \\`${headShort}\\`. ${aid}`);\n lines.push(\"\");\n lines.push(`<!-- plan-id: ${planId} -->`);\n return lines.join(\"\\n\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Subcommands */\n/* -------------------------------------------------------------------------- */\n\nfunction runScan(args: Record<string, string | boolean>): void {\n const diffPath = stringArg(args, \"diff\");\n const diffText = fs.readFileSync(path.resolve(diffPath), \"utf8\");\n if (diffContainsSecret(diffText)) {\n process.stdout.write(\n `${JSON.stringify({ suppressed: true, reason: \"potential secret in diff\" })}\\n`,\n );\n } else {\n process.stdout.write(`${JSON.stringify({ suppressed: false })}\\n`);\n }\n}\n\nfunction runBuildPrompt(args: Record<string, string | boolean>): void {\n const skill = readRepoSkillMd();\n const prompt = buildRecapPrompt({\n skillMd: skill.text,\n pr: stringArg(args, \"pr\"),\n head: optionalArg(args, \"head\"),\n appUrl: optionalArg(args, \"app-url\") ?? \"https://plan.agent-native.com\",\n diffPath: optionalArg(args, \"diff\") ?? \"recap.diff\",\n statPath: optionalArg(args, \"stat\"),\n prevPlanId: optionalArg(args, \"prev-plan-id\"),\n huge: args.huge === true || args.huge === \"true\",\n });\n const out = optionalArg(args, \"out\") ?? \"recap-prompt.md\";\n fs.writeFileSync(path.resolve(out), prompt);\n process.stdout.write(\n `${JSON.stringify({ ok: true, out, skillSource: skill.source, bytes: prompt.length })}\\n`,\n );\n}\n\n/** Upload a PNG to the plan app's signed public image route; returns its URL. */\nasync function uploadRecapImage(input: {\n appUrl: string;\n token: string;\n pngPath: string;\n}): Promise<string | null> {\n try {\n const base = input.appUrl.replace(/\\/$/, \"\");\n const bytes = fs.readFileSync(path.resolve(input.pngPath));\n const res = await fetch(`${base}/_agent-native/recap-image`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"image/png\",\n authorization: `Bearer ${input.token}`,\n },\n body: bytes,\n });\n // Surface failures on stderr — stdout carries the machine-readable JSON the\n // workflow parses, so it must stay clean. A silent null here is exactly what\n // made the missing-inline-thumbnail failure undebuggable from CI logs.\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n process.stderr.write(\n `[recap shot] image upload failed: ${res.status} ${res.statusText} ${detail.slice(0, 300)}\\n`,\n );\n return null;\n }\n const json = (await res.json().catch(() => null)) as {\n imageUrl?: string;\n } | null;\n if (!json?.imageUrl) {\n process.stderr.write(\n `[recap shot] image upload returned no imageUrl (status ${res.status})\\n`,\n );\n return null;\n }\n return json.imageUrl;\n } catch (err) {\n process.stderr.write(`[recap shot] image upload error: ${String(err)}\\n`);\n return null;\n }\n}\n\nasync function runShot(args: Record<string, string | boolean>): Promise<void> {\n const url = stringArg(args, \"url\");\n const out = optionalArg(args, \"out\") ?? \"recap.png\";\n const token = optionalArg(args, \"token\");\n const appUrl = optionalArg(args, \"app-url\");\n\n const done = (obj: Record<string, unknown>) => {\n process.stdout.write(`${JSON.stringify(obj)}\\n`);\n };\n\n // recap-url.txt is produced by the (LLM) agent, so the URL is untrusted. Only\n // forward the reusable publish token to the trusted plan-app origin — never to\n // an arbitrary URL — so a poisoned recap-url.txt can't exfiltrate the bearer\n // to an attacker-controlled domain.\n let attachToken = false;\n if (token) {\n try {\n attachToken = !!appUrl && new URL(url).origin === new URL(appUrl).origin;\n } catch {\n attachToken = false;\n }\n if (!attachToken) {\n done({\n ok: false,\n reason: appUrl\n ? `refusing to screenshot ${url}: origin does not match --app-url (${appUrl}); the publish token is only sent to the trusted plan app origin`\n : `refusing to attach the publish token without --app-url to validate ${url} against`,\n });\n return;\n }\n }\n\n let chromium: typeof import(\"playwright\").chromium | undefined;\n try {\n ({ chromium } = await import(\"playwright\"));\n } catch {\n try {\n ({ chromium } =\n (await import(\"@playwright/test\")) as unknown as typeof import(\"playwright\"));\n } catch (err) {\n done({ ok: false, reason: `playwright not available: ${String(err)}` });\n return;\n }\n }\n\n let captured = false;\n let browser: import(\"playwright\").Browser | undefined;\n const hardTimer = setTimeout(() => {\n done({ ok: false, reason: \"hard 60s timeout reached\" });\n process.exit(0);\n }, 60_000);\n try {\n browser = await chromium!.launch({ args: [\"--no-sandbox\"] });\n const context = await browser.newContext({\n viewport: { width: 1280, height: 900 },\n deviceScaleFactor: 2,\n });\n if (attachToken) {\n // Attach the bearer ONLY to same-origin requests. Context-wide\n // extraHTTPHeaders would also send it to every cross-origin subresource\n // the plan page loads (CDN images/fonts/scripts), leaking the publish\n // token; routing scopes it to the trusted app origin.\n const appOrigin = new URL(appUrl as string).origin;\n await context.route(\"**/*\", async (route) => {\n const request = route.request();\n if (new URL(request.url()).origin === appOrigin) {\n await route.continue({\n headers: { ...request.headers(), authorization: `Bearer ${token}` },\n });\n } else {\n await route.continue();\n }\n });\n }\n const page = await context.newPage();\n await page.goto(url, { waitUntil: \"networkidle\", timeout: 45_000 });\n const selectors = [\n \"[data-plan-document]\",\n \"[data-plan-block]\",\n \"main article\",\n \"[data-testid='plan-document']\",\n \"main\",\n ];\n let matched = false;\n for (const sel of selectors) {\n try {\n await page.waitForSelector(sel, { timeout: 6_000, state: \"visible\" });\n matched = true;\n break;\n } catch {\n /* try the next selector */\n }\n }\n await page.waitForTimeout(matched ? 1_200 : 500);\n await page.screenshot({ path: out, fullPage: true });\n captured = true;\n await browser.close();\n } catch (err) {\n clearTimeout(hardTimer);\n try {\n if (browser) await browser.close();\n } catch {\n /* ignore */\n }\n done({\n ok: false,\n reason: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n clearTimeout(hardTimer);\n\n let imageUrl: string | null = null;\n if (captured && token && appUrl) {\n imageUrl = await uploadRecapImage({ appUrl, token, pngPath: out });\n }\n done({ ok: captured, out, imageUrl });\n}\n\nasync function runComment(\n args: Record<string, string | boolean>,\n sub: string,\n): Promise<void> {\n const token = stringArg(args, \"token\");\n const { owner, repo } = repoParts(stringArg(args, \"repo\"));\n const issue = stringArg(args, \"issue\");\n\n if (sub === \"find-plan-id\") {\n const existing = await findExistingComment({ token, owner, repo, issue });\n const body = existing?.body ?? \"\";\n const match = body.match(/<!--\\s*plan-id:\\s*([^\\s]+)\\s*-->/);\n process.stdout.write(match ? match[1] : \"\");\n return;\n }\n\n if (sub === \"upsert\") {\n const result = await upsertComment({\n token,\n owner,\n repo,\n issue,\n body: buildCommentBody(),\n updateOnly:\n args[\"update-only\"] === true || args[\"update-only\"] === \"true\",\n });\n process.stdout.write(`${JSON.stringify(result)}\\n`);\n return;\n }\n\n throw new Error(\n \"Usage: agent-native recap comment <find-plan-id|upsert> --repo owner/name --issue n --token token\",\n );\n}\n\nconst HELP = `agent-native recap — PR visual recap helpers (used by the GitHub Action)\n\nUsage:\n agent-native recap scan --diff <path>\n agent-native recap build-prompt --pr <n> [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--prev-plan-id <id>] [--huge] [--out <path>]\n agent-native recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png]\n agent-native recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>\n`;\n\nexport async function runRecap(argv: string[]): Promise<void> {\n const [sub, ...rest] = argv;\n const args = parseArgs(rest);\n switch (sub) {\n case \"scan\":\n runScan(args);\n return;\n case \"build-prompt\":\n runBuildPrompt(args);\n return;\n case \"shot\":\n await runShot(args);\n return;\n case \"comment\":\n await runComment(parseArgs(rest.slice(1)), rest[0] ?? \"\");\n return;\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n process.stdout.write(HELP);\n return;\n default:\n process.stderr.write(`Unknown recap subcommand: ${sub}\\n${HELP}`);\n process.exit(1);\n }\n}\n"]}
1
+ {"version":3,"file":"recap.js","sourceRoot":"","sources":["../../src/cli/recap.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `agent-native recap <scan|build-prompt|shot|comment>` — the helper surface\n * used by the PR Visual Recap GitHub Action.\n *\n * The action no longer generates the recap deterministically. Instead a coding\n * agent (Claude Code or Codex) RUNS THE REPO'S visual-recap skill against the\n * diff and publishes the plan via the plan MCP tools. These subcommands are the\n * thin, deterministic glue around that:\n *\n * scan Refuse to hand a secret-leaking diff to the agent.\n * build-prompt Assemble the agent prompt = repo SKILL.md + a task wrapper.\n * shot Screenshot the published plan and upload it to the plan app's\n * signed public image route (for an inline PR-comment image).\n * comment Find the previous plan id / upsert the sticky PR comment.\n *\n * Promoting these to the published CLI means an installed repo's workflow calls\n * `agent-native recap …` instead of copying helper scripts into the repo.\n *\n * Node built-ins only (plus an optional dynamic `playwright` import for `shot`).\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { PR_VISUAL_RECAP_WORKFLOW_YML } from \"./pr-visual-recap-workflow.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Arg parsing */\n/* -------------------------------------------------------------------------- */\n\nfunction parseArgs(argv: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i += 1) {\n const token = argv[i];\n if (!token.startsWith(\"--\")) continue;\n const key = token.slice(2);\n const next = argv[i + 1];\n if (next === undefined || next.startsWith(\"--\")) out[key] = true;\n else {\n out[key] = next;\n i += 1;\n }\n }\n return out;\n}\n\nfunction stringArg(\n args: Record<string, string | boolean>,\n key: string,\n): string {\n const value = args[key];\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(`Missing --${key}`);\n }\n return value;\n}\n\nfunction optionalArg(\n args: Record<string, string | boolean>,\n key: string,\n): string | undefined {\n const value = args[key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/* -------------------------------------------------------------------------- */\n/* GitHub Action install (used by `skills add … --with-github-action`) */\n/* -------------------------------------------------------------------------- */\n\n/** GitHub secrets the installed PR Visual Recap workflow needs. */\nexport const PR_VISUAL_RECAP_SETUP: string[] = [\n \"Required secrets:\",\n \" PLAN_RECAP_TOKEN — bearer token from `agent-native connect`\",\n \" ANTHROPIC_API_KEY — the LLM key for the default Claude Code backend\",\n \"Optional (only if you change defaults):\",\n \" OPENAI_API_KEY (secret) + VISUAL_RECAP_AGENT=codex (variable) — use Codex instead of Claude\",\n \" VISUAL_RECAP_MODEL / VISUAL_RECAP_REASONING (variables) — pin the model (e.g. gpt-5.5) and reasoning depth (none|minimal|low|medium|high|xhigh; Codex only)\",\n \" PLAN_RECAP_APP_URL (secret) — only when self-hosting the plan app (defaults to https://plan.agent-native.com)\",\n];\n\n/** Write .github/workflows/pr-visual-recap.yml into a repo. */\nexport function writePrVisualRecapWorkflow(baseDir: string): {\n path: string;\n existed: boolean;\n} {\n const dir = path.resolve(baseDir, \".github\", \"workflows\");\n fs.mkdirSync(dir, { recursive: true });\n const file = path.join(dir, \"pr-visual-recap.yml\");\n const existed = fs.existsSync(file);\n fs.writeFileSync(file, PR_VISUAL_RECAP_WORKFLOW_YML);\n return { path: path.relative(baseDir, file), existed };\n}\n\n/* -------------------------------------------------------------------------- */\n/* Secret scan — defense-in-depth before any LLM sees the diff */\n/* -------------------------------------------------------------------------- */\n\n/**\n * If the diff contains anything that looks like a real secret, we refuse to\n * build a recap at all (rather than risk echoing it into a published plan).\n * These patterns intentionally err toward caution and scan added, removed, and\n * context lines so deleting a real secret does not leak it in a split diff.\n */\nconst SECRET_PATTERNS: RegExp[] = [\n // Common provider key prefixes.\n /\\b(?:sk|pk|rk)-[A-Za-z0-9]{16,}\\b/,\n /\\bghp_[A-Za-z0-9]{20,}\\b/,\n /\\bgithub_pat_[A-Za-z0-9_]{20,}\\b/,\n /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAKIA[0-9A-Z]{16}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n // Bearer / Authorization header values with an actual token.\n /authorization\\s*[:=]\\s*['\"]?bearer\\s+[A-Za-z0-9._-]{12,}/i,\n // Private key blocks.\n /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/,\n // `KEY=...`, `TOKEN=...`, `SECRET=...`, `PASSWORD=...` assigned a real-looking\n // value (long, non-placeholder).\n /\\b[A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|API_KEY|PRIVATE_KEY|ACCESS_KEY)[A-Z0-9_]*\\s*[:=]\\s*['\"]?(?!.*(?:your|example|placeholder|changeme|xxxx|\\*\\*\\*|<|\\$\\{|process\\.env|env\\.|REDACTED))[A-Za-z0-9/_+=.-]{16,}/i,\n];\n\nexport function lineLooksSecret(line: string): boolean {\n return SECRET_PATTERNS.some((re) => re.test(line));\n}\n\nexport function diffContainsSecret(diffText: string): boolean {\n for (const line of diffText.split(\"\\n\")) {\n if (\n line.startsWith(\"+\") ||\n line.startsWith(\"-\") ||\n line.startsWith(\" \") ||\n line.startsWith(\"+++\") ||\n line.startsWith(\"---\")\n ) {\n if (lineLooksSecret(line)) return true;\n }\n }\n return false;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Prompt builder — repo SKILL.md + task wrapper */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Locate the repo's visual-recap SKILL.md, preferring the host-agent install\n * locations so a user's `agent-native skills add` copy wins, then falling back\n * to the framework's own source locations.\n */\nexport function readRepoSkillMd(cwd: string = process.cwd()): {\n text: string;\n source: string;\n} {\n const candidates = [\n \".claude/skills/visual-recap/SKILL.md\",\n \".agents/skills/visual-recap/SKILL.md\",\n \"skills/visual-recap/SKILL.md\",\n \"templates/plan/.agents/skills/visual-recap/SKILL.md\",\n ];\n for (const rel of candidates) {\n const abs = path.resolve(cwd, rel);\n if (fs.existsSync(abs)) {\n return { text: fs.readFileSync(abs, \"utf8\"), source: rel };\n }\n }\n throw new Error(\n \"Could not find visual-recap/SKILL.md. Run `agent-native skills add visual-plan` first.\",\n );\n}\n\nexport function buildRecapPrompt(input: {\n skillMd: string;\n pr: string;\n head?: string;\n appUrl: string;\n diffPath: string;\n statPath?: string;\n prevPlanId?: string;\n huge?: boolean;\n}): string {\n const appUrl = input.appUrl.replace(/\\/$/, \"\");\n const lines: string[] = [];\n lines.push(\"# Task: publish a Visual Recap of this pull request\");\n lines.push(\"\");\n lines.push(\n `You are running non-interactively in CI. Follow the **visual-recap skill** included verbatim below to turn this PR's diff into a grounded Agent-Native Plan, then publish it.`,\n );\n lines.push(\"\");\n lines.push(\"## Inputs (read them from disk with your Read tool)\");\n lines.push(`- PR number: **#${input.pr}**`);\n if (input.head) lines.push(`- Head commit: \\`${input.head}\\``);\n lines.push(`- Unified diff: \\`${input.diffPath}\\` (read this file)`);\n if (input.statPath)\n lines.push(`- Diff stat: \\`${input.statPath}\\` (read this file)`);\n if (input.huge) {\n lines.push(\n `- The diff is LARGE — produce a **summarized** recap (top files + schema/API deltas), not an exhaustive one.`,\n );\n }\n lines.push(\"\");\n lines.push(\"## Publish (this is the only way to produce output)\");\n lines.push(\n `The \\`plan\\` MCP server is configured for you. Call its tools by name (your host may expose them as \\`create-visual-recap\\` or \\`mcp__plan__create-visual-recap\\` — same tool).`,\n );\n lines.push(\n `1. Call the **create-visual-recap** tool on the \\`plan\\` MCP server with grounded MDX derived ONLY from the real diff${\n input.prevPlanId\n ? `, passing \\`planId: \"${input.prevPlanId}\"\\` so this REPLACES the existing recap plan`\n : \"\"\n }.`,\n );\n lines.push(\n `2. Call the **set-resource-visibility** tool on the \\`plan\\` MCP server with \\`{ resourceType: \"plan\", resourceId: <the returned plan id>, visibility: \"org\" }\\` so the recap is login-gated to the org, never public.`,\n );\n lines.push(\n `3. Write the plan URL to a file named \\`recap-url.txt\\` at the repo root, containing exactly one line: \\`${appUrl}/recaps/<the returned plan id>\\`. This file is the workflow's only hand-off — do not print anything else as the deliverable.`,\n );\n lines.push(\"\");\n lines.push(\n \"Do not invent file names, schema fields, or endpoints. Redact anything that looks like a secret. If the diff has no reviewable substance, still publish a minimal recap and write recap-url.txt.\",\n );\n lines.push(\"\");\n lines.push(\"---\");\n lines.push(\"\");\n lines.push(\"# visual-recap skill (follow this exactly)\");\n lines.push(\"\");\n lines.push(input.skillMd.trim());\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* GitHub comment helpers */\n/* -------------------------------------------------------------------------- */\n\nconst MARKER = \"<!-- pr-visual-recap -->\";\n\ntype GitHubComment = {\n id: number;\n body?: string | null;\n html_url?: string;\n user?: { type?: string | null } | null;\n};\n\nfunction repoParts(repoFullName: string): { owner: string; repo: string } {\n const [owner, repo] = repoFullName.split(\"/\");\n if (!owner || !repo) throw new Error(`Invalid --repo: ${repoFullName}`);\n return { owner, repo };\n}\n\nasync function githubRequest<T>(\n token: string,\n apiPath: string,\n init: RequestInit = {},\n): Promise<T> {\n const res = await fetch(`https://api.github.com${apiPath}`, {\n ...init,\n headers: {\n accept: \"application/vnd.github+json\",\n authorization: `Bearer ${token}`,\n \"x-github-api-version\": \"2022-11-28\",\n ...(init.headers ?? {}),\n },\n });\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new Error(\n `GitHub request failed ${res.status} ${res.statusText}: ${detail.slice(0, 500)}`,\n );\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\nasync function findExistingComment(input: {\n token: string;\n owner: string;\n repo: string;\n issue: string;\n}): Promise<GitHubComment | null> {\n for (let page = 1; ; page += 1) {\n const comments = await githubRequest<GitHubComment[]>(\n input.token,\n `/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(\n input.repo,\n )}/issues/${encodeURIComponent(input.issue)}/comments?per_page=100&page=${page}`,\n );\n const match = comments.find(\n (comment) =>\n comment.user?.type === \"Bot\" &&\n typeof comment.body === \"string\" &&\n comment.body.includes(MARKER),\n );\n if (match) return match;\n if (comments.length < 100) return null;\n }\n}\n\nasync function upsertComment(input: {\n token: string;\n owner: string;\n repo: string;\n issue: string;\n body: string;\n /** When true, refresh an existing comment but never create a new one. */\n updateOnly?: boolean;\n}): Promise<{\n action: \"created\" | \"updated\" | \"skipped\";\n id: number;\n html_url?: string;\n}> {\n const body = input.body.includes(MARKER)\n ? input.body\n : `${MARKER}\\n${input.body}`;\n const existing = await findExistingComment(input);\n if (!existing && input.updateOnly) {\n // Nothing to refresh and we were told not to create — e.g. a tiny diff with\n // no prior recap. Stay silent rather than posting a \"skipped\" comment.\n return { action: \"skipped\", id: 0 };\n }\n if (existing) {\n const updated = await githubRequest<GitHubComment>(\n input.token,\n `/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(\n input.repo,\n )}/issues/comments/${existing.id}`,\n {\n method: \"PATCH\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ body }),\n },\n );\n return { action: \"updated\", id: existing.id, html_url: updated.html_url };\n }\n const created = await githubRequest<GitHubComment>(\n input.token,\n `/repos/${encodeURIComponent(input.owner)}/${encodeURIComponent(\n input.repo,\n )}/issues/${encodeURIComponent(input.issue)}/comments`,\n {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ body }),\n },\n );\n return { action: \"created\", id: created.id, html_url: created.html_url };\n}\n\nfunction planIdFromUrl(url: string): string | null {\n // Accept both /recaps/<id> (the canonical recap route the agent now writes)\n // and /plans/<id> (legacy URLs) so the sticky-comment rebuild keeps working.\n const match = url.match(/\\/(?:recaps|plans)\\/([A-Za-z0-9_-]+)/);\n return match ? match[1] : null;\n}\n\n/** True when both URLs parse and share an origin. */\nfunction sameOrigin(a: string, b: string): boolean {\n try {\n return new URL(a).origin === new URL(b).origin;\n } catch {\n return false;\n }\n}\n\n/** The origin of a URL, or \"\" if it doesn't parse. */\nfunction originOf(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return \"\";\n }\n}\n\n/** Build the sticky comment body from the workflow's environment. */\nexport function buildCommentBody(env: NodeJS.ProcessEnv = process.env): string {\n const headShort = (env.HEAD_SHA || \"\").slice(0, 7);\n const lines: string[] = [MARKER];\n\n if (env.SUPPRESSED === \"true\") {\n let reason = \"potential secret in diff\";\n try {\n const parsed = JSON.parse(env.SUPPRESSED_JSON || \"{}\");\n if (parsed && typeof parsed.reason === \"string\") reason = parsed.reason;\n } catch {\n /* keep default */\n }\n lines.push(\"### Visual recap — not generated\");\n lines.push(\"\");\n lines.push(\n \"The recap was **suppressed** because the diff matched a secret/credential pattern. No plan was published.\",\n );\n lines.push(\"\");\n lines.push(`Reason: \\`${reason}\\`. Updated for \\`${headShort}\\`.`);\n return lines.join(\"\\n\");\n }\n\n // Tiny diffs aren't worth a recap. Refresh an existing sticky comment to this\n // state (the workflow only updates, never creates, on tiny) so it never lingers\n // pointing at a stale head SHA.\n if (env.DIFF_TINY === \"true\") {\n lines.push(\"### Visual recap — skipped (diff too small)\");\n lines.push(\"\");\n lines.push(\n \"The change in this push is too small to be worth a visual recap. This is informational only and does **not** block the PR.\",\n );\n lines.push(\"\");\n lines.push(`Updated for \\`${headShort}\\`.`);\n return lines.join(\"\\n\");\n }\n\n const planUrl = (env.PLAN_URL || \"\").trim();\n const appUrl = (env.PLAN_RECAP_APP_URL || \"\").trim();\n // recap-url.txt is agent-written → untrusted. Rebuild a canonical link from a\n // TRUSTED base (the configured PLAN_RECAP_APP_URL when set, else the parsed\n // origin of the plan URL) plus a strictly-validated plan id, instead of\n // embedding the raw URL. That both enforces the app origin and prevents\n // markdown injection — a same-origin URL with a crafted path/query could\n // otherwise break out of the markdown link.\n const planId = planUrl ? planIdFromUrl(planUrl) : null;\n const sameOriginOk = appUrl === \"\" || sameOrigin(planUrl, appUrl);\n const base = (appUrl || originOf(planUrl)).replace(/\\/$/, \"\");\n const safeUrl =\n planId && base && sameOriginOk ? `${base}/recaps/${planId}` : \"\";\n if (!safeUrl) {\n lines.push(\"### Visual recap — generation failed\");\n lines.push(\"\");\n lines.push(\n \"The visual recap could not be generated for this push. This is informational only and does **not** block the PR.\",\n );\n lines.push(\"\");\n lines.push(`Updated for \\`${headShort}\\`.`);\n return lines.join(\"\\n\");\n }\n\n // The image URL is produced by our own recap-image route, but validate it is\n // same-origin and matches the canonical hex-token path before embedding it, so\n // it likewise cannot inject markdown.\n const imageUrlRaw = (env.RECAP_IMAGE_URL || \"\").trim();\n const imageUrl =\n imageUrlRaw &&\n sameOrigin(imageUrlRaw, base) &&\n /\\/_agent-native\\/recap-image\\/[0-9a-f]+\\.png$/.test(imageUrlRaw)\n ? imageUrlRaw\n : \"\";\n lines.push(\"### Visual recap — review at a higher altitude\");\n lines.push(\"\");\n if (imageUrl) {\n lines.push(`[![Visual recap](${imageUrl})](${safeUrl})`);\n lines.push(\"\");\n }\n lines.push(`**[Open the interactive recap](${safeUrl})**`);\n if (env.DIFF_HUGE === \"true\") {\n lines.push(\"\");\n lines.push(\n \"> Large diff — this recap is a **summarized** view (top files + schema/API deltas).\",\n );\n }\n lines.push(\"\");\n lines.push(`Updated for \\`${headShort}\\`.`);\n lines.push(\"\");\n lines.push(`<!-- plan-id: ${planId} -->`);\n return lines.join(\"\\n\");\n}\n\n/* -------------------------------------------------------------------------- */\n/* Subcommands */\n/* -------------------------------------------------------------------------- */\n\nfunction runScan(args: Record<string, string | boolean>): void {\n const diffPath = stringArg(args, \"diff\");\n const diffText = fs.readFileSync(path.resolve(diffPath), \"utf8\");\n if (diffContainsSecret(diffText)) {\n process.stdout.write(\n `${JSON.stringify({ suppressed: true, reason: \"potential secret in diff\" })}\\n`,\n );\n } else {\n process.stdout.write(`${JSON.stringify({ suppressed: false })}\\n`);\n }\n}\n\nfunction runBuildPrompt(args: Record<string, string | boolean>): void {\n const skill = readRepoSkillMd();\n const prompt = buildRecapPrompt({\n skillMd: skill.text,\n pr: stringArg(args, \"pr\"),\n head: optionalArg(args, \"head\"),\n appUrl: optionalArg(args, \"app-url\") ?? \"https://plan.agent-native.com\",\n diffPath: optionalArg(args, \"diff\") ?? \"recap.diff\",\n statPath: optionalArg(args, \"stat\"),\n prevPlanId: optionalArg(args, \"prev-plan-id\"),\n huge: args.huge === true || args.huge === \"true\",\n });\n const out = optionalArg(args, \"out\") ?? \"recap-prompt.md\";\n fs.writeFileSync(path.resolve(out), prompt);\n process.stdout.write(\n `${JSON.stringify({ ok: true, out, skillSource: skill.source, bytes: prompt.length })}\\n`,\n );\n}\n\n/** Upload a PNG to the plan app's signed public image route; returns its URL. */\nasync function uploadRecapImage(input: {\n appUrl: string;\n token: string;\n pngPath: string;\n}): Promise<string | null> {\n try {\n const base = input.appUrl.replace(/\\/$/, \"\");\n const bytes = fs.readFileSync(path.resolve(input.pngPath));\n const res = await fetch(`${base}/_agent-native/recap-image`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"image/png\",\n authorization: `Bearer ${input.token}`,\n },\n body: bytes,\n });\n // Surface failures on stderr — stdout carries the machine-readable JSON the\n // workflow parses, so it must stay clean. A silent null here is exactly what\n // made the missing-inline-thumbnail failure undebuggable from CI logs.\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n process.stderr.write(\n `[recap shot] image upload failed: ${res.status} ${res.statusText} ${detail.slice(0, 300)}\\n`,\n );\n return null;\n }\n const json = (await res.json().catch(() => null)) as {\n imageUrl?: string;\n } | null;\n if (!json?.imageUrl) {\n process.stderr.write(\n `[recap shot] image upload returned no imageUrl (status ${res.status})\\n`,\n );\n return null;\n }\n return json.imageUrl;\n } catch (err) {\n process.stderr.write(`[recap shot] image upload error: ${String(err)}\\n`);\n return null;\n }\n}\n\nasync function runShot(args: Record<string, string | boolean>): Promise<void> {\n const url = stringArg(args, \"url\");\n const out = optionalArg(args, \"out\") ?? \"recap.png\";\n const token = optionalArg(args, \"token\");\n const appUrl = optionalArg(args, \"app-url\");\n\n const done = (obj: Record<string, unknown>) => {\n process.stdout.write(`${JSON.stringify(obj)}\\n`);\n };\n\n // recap-url.txt is produced by the (LLM) agent, so the URL is untrusted. Only\n // forward the reusable publish token to the trusted plan-app origin — never to\n // an arbitrary URL — so a poisoned recap-url.txt can't exfiltrate the bearer\n // to an attacker-controlled domain.\n let attachToken = false;\n if (token) {\n try {\n attachToken = !!appUrl && new URL(url).origin === new URL(appUrl).origin;\n } catch {\n attachToken = false;\n }\n if (!attachToken) {\n done({\n ok: false,\n reason: appUrl\n ? `refusing to screenshot ${url}: origin does not match --app-url (${appUrl}); the publish token is only sent to the trusted plan app origin`\n : `refusing to attach the publish token without --app-url to validate ${url} against`,\n });\n return;\n }\n }\n\n let chromium: typeof import(\"playwright\").chromium | undefined;\n try {\n ({ chromium } = await import(\"playwright\"));\n } catch {\n try {\n ({ chromium } =\n (await import(\"@playwright/test\")) as unknown as typeof import(\"playwright\"));\n } catch (err) {\n done({ ok: false, reason: `playwright not available: ${String(err)}` });\n return;\n }\n }\n\n let captured = false;\n let browser: import(\"playwright\").Browser | undefined;\n const hardTimer = setTimeout(() => {\n done({ ok: false, reason: \"hard 60s timeout reached\" });\n process.exit(0);\n }, 60_000);\n try {\n browser = await chromium!.launch({ args: [\"--no-sandbox\"] });\n const context = await browser.newContext({\n viewport: { width: 1450, height: 1450 },\n deviceScaleFactor: 2,\n });\n if (attachToken) {\n // Attach the bearer ONLY to same-origin requests. Context-wide\n // extraHTTPHeaders would also send it to every cross-origin subresource\n // the plan page loads (CDN images/fonts/scripts), leaking the publish\n // token; routing scopes it to the trusted app origin.\n const appOrigin = new URL(appUrl as string).origin;\n await context.route(\"**/*\", async (route) => {\n const request = route.request();\n if (new URL(request.url()).origin === appOrigin) {\n await route.continue({\n headers: { ...request.headers(), authorization: `Bearer ${token}` },\n });\n } else {\n await route.continue();\n }\n });\n }\n const page = await context.newPage();\n await page.goto(url, { waitUntil: \"networkidle\", timeout: 45_000 });\n const selectors = [\n \"[data-plan-document]\",\n \"[data-plan-block]\",\n \"main article\",\n \"[data-testid='plan-document']\",\n \"main\",\n ];\n let matched = false;\n for (const sel of selectors) {\n try {\n await page.waitForSelector(sel, { timeout: 6_000, state: \"visible\" });\n matched = true;\n break;\n } catch {\n /* try the next selector */\n }\n }\n await page.waitForTimeout(matched ? 1_200 : 500);\n // Zoom out slightly so more content fits, then scroll past the main title.\n await page.evaluate(() => {\n (document.documentElement as HTMLElement).style.zoom = \"80%\";\n });\n await page.evaluate(() => {\n const firstH2 = document.querySelector(\"h2\");\n if (firstH2) {\n firstH2.scrollIntoView({ block: \"start\" });\n window.scrollBy(0, -24);\n } else {\n const h1 = document.querySelector(\"h1\");\n if (h1) window.scrollBy(0, h1.getBoundingClientRect().bottom + 32);\n }\n });\n await page.screenshot({ path: out });\n captured = true;\n await browser.close();\n } catch (err) {\n clearTimeout(hardTimer);\n try {\n if (browser) await browser.close();\n } catch {\n /* ignore */\n }\n done({\n ok: false,\n reason: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n clearTimeout(hardTimer);\n\n let imageUrl: string | null = null;\n if (captured && token && appUrl) {\n imageUrl = await uploadRecapImage({ appUrl, token, pngPath: out });\n }\n done({ ok: captured, out, imageUrl });\n}\n\nasync function runComment(\n args: Record<string, string | boolean>,\n sub: string,\n): Promise<void> {\n const token = stringArg(args, \"token\");\n const { owner, repo } = repoParts(stringArg(args, \"repo\"));\n const issue = stringArg(args, \"issue\");\n\n if (sub === \"find-plan-id\") {\n const existing = await findExistingComment({ token, owner, repo, issue });\n const body = existing?.body ?? \"\";\n const match = body.match(/<!--\\s*plan-id:\\s*([^\\s]+)\\s*-->/);\n process.stdout.write(match ? match[1] : \"\");\n return;\n }\n\n if (sub === \"upsert\") {\n const result = await upsertComment({\n token,\n owner,\n repo,\n issue,\n body: buildCommentBody(),\n updateOnly:\n args[\"update-only\"] === true || args[\"update-only\"] === \"true\",\n });\n process.stdout.write(`${JSON.stringify(result)}\\n`);\n return;\n }\n\n throw new Error(\n \"Usage: agent-native recap comment <find-plan-id|upsert> --repo owner/name --issue n --token token\",\n );\n}\n\nconst HELP = `agent-native recap — PR visual recap helpers (used by the GitHub Action)\n\nUsage:\n agent-native recap scan --diff <path>\n agent-native recap build-prompt --pr <n> [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--prev-plan-id <id>] [--huge] [--out <path>]\n agent-native recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png]\n agent-native recap comment <find-plan-id|upsert> --repo owner/name --issue <n> --token <github-token>\n`;\n\nexport async function runRecap(argv: string[]): Promise<void> {\n const [sub, ...rest] = argv;\n const args = parseArgs(rest);\n switch (sub) {\n case \"scan\":\n runScan(args);\n return;\n case \"build-prompt\":\n runBuildPrompt(args);\n return;\n case \"shot\":\n await runShot(args);\n return;\n case \"comment\":\n await runComment(parseArgs(rest.slice(1)), rest[0] ?? \"\");\n return;\n case \"help\":\n case \"--help\":\n case \"-h\":\n case undefined:\n process.stdout.write(HELP);\n return;\n default:\n process.stderr.write(`Unknown recap subcommand: ${sub}\\n${HELP}`);\n process.exit(1);\n }\n}\n"]}